<?php

class Catalogue_Model_Category extends Catalogue_Model_Object_Abstract implements Workset_Model_Object_TreeNode_Interface {

	const T_OBJECT = 'object';
	const T_ATTRIBUTE = 'attribute';
	const T_MEDIA = 'media';
	const T_MEDIASET = 'mediaset';

    /**
     * Тип объекта
     * @var string
     */
    protected $_objectType = Catalogue_Model_Object_Abstract::T_CATEGORY;

    protected $_hasRelations = true;

    /**
     * Возвращает объект Catalogue_Model_Category по primary key
     *
     * @static
     * @param  mixed $identity
     * @return Catalogue_Model_Category
     *
     */
    static function getInstance($identity) {
        $storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_CATEGORY);
        return self::getFromStorage($storage, $identity);
    }

    /**
     * Создает новый объект Catalogue_Model_Category с полями $data
     *
     * @static
     * @param  array $data
     * @return Catalogue_Model_Category
     *
     */
    static function create($data) {
        $storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_CATEGORY);
        return $storage->create($data);
    }

    /**
     * Возвращает список категорий
     * 
     * @static
     * @param  mixed $where
     * @param  mixed $order
     * @param  int $limit
     * @param  int $offset
     * @return Catalogue_Model_Object_Iterator
     *
     */
    static function getAll($where = null, $order = null, $limit = null, $offset = null) {
        if ($limit > 0 && $offset > 0) {
            $offset = ($limit * ($offset - 1));
        }
        $storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_CATEGORY);
        return $storage->getAll($where, $order, $limit, $offset);
    }

    static function getByCode($code) {
        $storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_CATEGORY);
        return $storage->getAll(array('code = ?' => $code));
    }

    static function getTypes($format = 'array', $labels = array()) {
        $types = array(
            self::T_OBJECT,
            self::T_ATTRIBUTE,
            self::T_MEDIA,
            self::T_MEDIASET
        );

        if ($format == 'array') {
            return $types;
        }

        if ($format == 'jqgrid') {
            $namedTypes = array();

            if (empty($labels)) {
                foreach ($types as $type) {
                    $namedTypes[] = $type.":".$type;
                }
            } else {
                foreach ($types as $type) {
                    if (isset($labels[$type])) {
                        $label = $labels[$type];
                    } else {
                        $label = $type;
                    }
                    $namedTypes[] = $type.':'.$label;
                }
            }

            return implode(';', $namedTypes);
        }
    }

    /**
     * Возвращает список атрибутов принадлежащих категории (или списку категорий)
     *
     * @static
     * @param   array|Catalogue_Model_Category|Iterator $categories - категории
     * @param   bool $groupAttrs - группировать список атрибутов по категории
     * @param   bool $checkParents - учитывать атрибуты родительских категорий
     * @param  $where
     * @param  $order
     * @param  $limit
     * @param  $offset
     * @return array
     */
    static function getCategoryAttributes($categories, $groupAttrs = false, $checkParents = false,
                                          $where = null, $order = null, $limit = null, $offset = null)
    {
        if (!is_array($categories) && !$categories instanceof Iterator) {
            $categories = array($categories);
        }

        $result = array();
        $listId = array();

        foreach ($categories as $category) {
            if (!$category instanceof Catalogue_Model_Category) {
                continue;
            }

            $categoryAttrs = $category->getAttributes($where, $order, $limit, $offset, $checkParents);

            foreach ($categoryAttrs as $attr) {
                $id = $attr->getId();

                if (in_array($id, $listId)) {
                    continue;
                }

                $listId[] = $id;

                if ($groupAttrs) {
                    $result[$category->getId()][$id] = $attr;
                } else {
                    $result[$id] = $attr;
                }
            }

        }

        return $result;
    }

    function init() {
        $this->attach(new Workset_Model_Observer_TreeNodeUpdater('categories'));
    }

    /**
     * Сохраняет категорию (добавляет в хранилище новую категорию или обновляет существующую)
     *
     * @return string
     */
    function save() {
        $this['depth'] = $this->_calcDepth();
        return parent::save();
    }

    function linkToObject(Workset_Model_Object_Abstract $object, $data = null) {
        if (null === $this->_storage) {
            $storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_CATEGORY);
            $this->setStorage($storage);
        }

        $result = parent::linkToObject($object, $data);
        if ($result) {
            return $this->incrementObjectsCount()->save();
        }
    }

    function unlinkObject(Workset_Model_Object_Abstract $object) {
        if (null === $this->_storage) {
            $storage = self::getDefaultStorage(Catalogue_Model_Object_Abstract::T_CATEGORY);
            $this->setStorage($storage);
        }

        $result = parent::unlinkObject($object);
        if ($result) {
            return $this->decrementObjectsCount()->save();
        }
    }

    /**
     * Устанавливает ID родительской категории
     *
     * @param  int $id
     * @return Catalogue_Model_Category
     */
    function setParentId($id) {
        $id = (int)$id;
        if ($id > 0 && $id != $this->getId()) {
            $this->_data['parent_id'] = $id;
        }
        return $this;
    }

    /**
     * Возвращает ID родителя
     *
     * @return int
     */
    function getParentId() {
        return (int)$this->_data['parent_id'];
    }

    /**
     * Устанавливает родительскую категорию
     *
     * @param Workset_Model_Object_TreeNode_Interface $parent
     * @return Catalogue_Model_Category
     */
    function setParent(Workset_Model_Object_TreeNode_Interface $parent) {
        if ($parent instanceof Catalogue_Model_Category) {
            $this->setParentId($parent->getId());
        }
        return $this;
    }

    /**
     * Возвращает родительскую категорию
     *
     * @return Catalogue_Model_Category
     *
     */
    function getParent() {
        $parentId = $this->getParentId();
        if ($parentId > 0) {
            return Catalogue_Model_Category::getInstance($parentId);
        }
        return null;
    }

    /**
     * @return bool
     *
     * Проверяет, есть ли у категории родительская категория
     */
    function hasParent() {
        return $this->_data['parent_id'] > 0;
    }

    /**
     * Возвращает дерево родителей категории
     * @return array
     */
    function getParents() {
        $result = array();
        $parent = $this;

        while (	$parent = $parent->getParent() ) {
            $result[] = $parent->getData();
        }

        if ( $result ) {
            $result = array_reverse( $result );
        }

        return $this->getStorage()->getIterator($result);
    }

    /**
     * Возвращает дочерние категории 1-го уровня вложенности
     *
     * @param  $order
     * @param  $limit
     * @param  $offset
     * @return Catalogue_Model_Object_Iterator
     */
    function getChilds($where = null, $order = null, $limit = null, $offset = null) {
        $where = (array)$where;
        $where[] = "parent_id = {$this->getId()}";
        return self::getAll($where, $order, $limit, $offset);
    }

    /**
     * Есть ли дочерние категории
     *
     * @return bool
     */
    function hasChilds() {
        return $this['categories'] > 0;
    }

    /**
     * Добавляет атрибут к категории
     *
     * @param  $attr
     * @return Catalogue_Model_Category
     */
    function addAttribute($attr, $data = null) {
        if (!$attr instanceof Catalogue_Model_Attribute) {
            $attr = Catalogue_Model_Attribute::getInstance($attr);
        }

        $this->linkToObject($attr, $data);

        return $this;
    }

    /**
     * Удаляет атрибут из списка атрибутов категории
     *
     * @param  $attr
     * @return Catalogue_Model_Category
     */
    function deleteAttribute($attr) {
        if (!$attr instanceof Catalogue_Model_Attribute) {
            $attr = Catalogue_Model_Attribute::getInstance($attr);
        }

        $this->unlinkObject($attr);

        return $this;
    }

    /**
     * Возвращает список атрибутов категории
     *
     * @return Catalogue_Model_Object_Iterator
     */
    function getAttributes($where = null, $order = null, $limit = null, $offset = null, $checkParents = false) {
        $offset = self::_getSqlOffset($limit, $offset);

        //@todo
        if (empty($order)) {
           $order = array('i.priority ASC', 'm.sort ASC', 'm.id DESC');
        }

        return $this->getStorage()->getAttributes($this, $where, $order, $limit, $offset, $checkParents);
    }

    /**
     * Возвращает объекты текущей категории
     *
     * @param  $where
     * @param  $order
     * @param  $limit
     * @param  $offset
     * @return Catalogue_Model_Object_Iterator
     */
    function getObjects($where = null, $order = null, $limit = null, $offset = null) {
        $where = (array)$where;
        $where[] = "category_id = {$this->getId()}";
        return Catalogue_Model_Object::getAll($where, $order, $limit, $offset);
    }

    /**
     * Содержит ли вложенные категории или объекты
     *
     * @return bool
     */
    function isEmpty() {
        return $this['objects'] == 0 && $this['categories'] == 0;
    }

    /**
     * Возвращает уровень вложенности категории
     *
     * @return int
     */
    function getDepth() {
        return (int)$this['depth'];
    }

    /**
     * Возвращает индекс сортировки категории
     *
     * @return int
     */
    function getSortIndex() {
        return (int)$this['sort'];
    }

    /**
     * Возвращает название категории
     *
     * @return string
     */
    function getName() {
        return $this['name'];
    }

    /**
     * Увеличивает кол-во объектов в категории
     *
     * @return Catalogue_Model_Category
     */
    function incrementObjectsCount() {
        $this['objects'] += 1;
        return $this;
    }

    /**
     * Уменьшает кол-во объектов в категории
     *
     * @return Catalogue_Model_Category
     */
    function decrementObjectsCount() {
        if ($this['objects'] > 0) {
            $this['objects'] -= 1;
        }
        return $this;
    }

    /**
     * Увеличивает счетчик вложенных категорий
     *
     * @deprecated
     * @return Catalogue_Model_Category
     */
    function incrementCategoriesCount() {
        $this['categories'] += 1;
        return $this;
    }

    /**
     * Уменьшает счетчик вложенных категорий
     *
     * @deprecated
     * @return Catalogue_Model_Category
     */
    function decrementCategoriesCount() {
        if ($this['categories'] > 0) {
            $this['categories'] -= 1;
        }
        return $this;
    }

    /**
     * Возвращает число дочерних категорий
     *
     * @return int
     */
    function countCategories() {
        return (int)$this['categories'];
    }

    /**
     * Возвращает число привязанных объектов
     *
     * @return int
     */
    function countObjects() {
        return (int)$this['objects'];
    }

    /**
     * Вычисляет уровень вложенности категории
     *
     * @return int
     */
    protected function _calcDepth() {
        if (!$this->hasParent()) {
            return 0;
        } else {
            return $this->getParent()->getDepth() + 1;
        }
    }


}

?>